﻿using System;
using System.Net;
using System.Text;
using System.Net.Sockets;
using System.Threading;

namespace UT3Bots.Communications
{
    public class UT3Connection
    {
        private static int BUFFER_SIZE = 102400;

        public event EventHandler<TcpDataEventArgs> OnDataReceived;
        public event EventHandler<TcpErrorEventArgs> OnErrorOccurred;

        private Socket _socket;
        private DnsEndPoint _endPoint;
        private bool _isConnected;
        private byte[] _transferBuffer;

        private string _server;
        private int _port;


        #region Properties
        internal bool IsConnected
        {
            get
            {
                return this._isConnected;
            }
        }
        #endregion


        internal UT3Connection(string server, int port)
        {
            this._server = server;
            this._port = port;
            this._endPoint = new DnsEndPoint(this._server, this._port);
            this._transferBuffer = new byte[BUFFER_SIZE];

            Connect();
        }

        internal bool Connect()
        {
            if (IsConnected)
            {
                return false;
            }

            this._socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            SocketAsyncEventArgs context = new SocketAsyncEventArgs();
            context.UserToken = this._socket;
            context.RemoteEndPoint = this._endPoint;
            context.Completed += new EventHandler<SocketAsyncEventArgs>(socketConnection_OnOperationComplete);

            if (this._socket.ConnectAsync(context) == false)
            {
                HandleConnect(context);
            }

            return true;
        }


        private void socketConnection_OnOperationComplete(object sender, SocketAsyncEventArgs e)
        {
            Thread.Sleep(100);
            if (e.SocketError != SocketError.Success)
            {
                //Socket Failure
                this._isConnected = false;

                if (OnErrorOccurred != null)
                {
                    OnErrorOccurred.Invoke(this, new TcpErrorEventArgs(new Exception(e.SocketError.ToString())));
					return;
                }
            }

            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    HandleConnect(e);
                    break;

                case SocketAsyncOperation.Receive:
                    HandleReceive(e);
                    break;

                case SocketAsyncOperation.Send:

                    break;
            }
        }

        private void HandleConnect(SocketAsyncEventArgs e)
        {
            e.SetBuffer(this._transferBuffer, 0, this._transferBuffer.Length);
            this._socket.ReceiveAsync(e);
        }


        private void HandleReceive(SocketAsyncEventArgs e)
        {
            byte[] data = new byte[e.BytesTransferred];
            Buffer.BlockCopy(this._transferBuffer, e.Offset, data, 0, e.BytesTransferred);

            if (OnDataReceived != null)
            {
                OnDataReceived.Invoke(this, new TcpDataEventArgs(data));
            }

            this._socket.ReceiveAsync(e);
        }



        internal void Disconnect()
        {
            this._isConnected = false;
            this._socket.Close();
        }


        internal void SendLine(string Message)
        {
            if (this._isConnected)
            {
                byte[] bytes = Encoding.UTF8.GetBytes(Message);

                SocketAsyncEventArgs context = new SocketAsyncEventArgs();
                context.SetBuffer(bytes, 0, bytes.Length);
                context.UserToken = this._socket;
                context.RemoteEndPoint = this._endPoint;
                context.Completed += new EventHandler<SocketAsyncEventArgs>(socketConnection_OnOperationComplete);

                this._socket.SendAsync(context);
            }
            else
            {
                if (OnErrorOccurred != null)
                {
                    OnErrorOccurred.Invoke(this, new TcpErrorEventArgs(new Exception(SocketError.NotConnected.ToString())));
                }

            }
        }
    }



    public class TcpDataEventArgs : EventArgs
    {
        private byte[] _data;

        #region Properties
        public byte[] Data
        {
            get { return this._data; }
        }
        #endregion

        public TcpDataEventArgs(byte[] Data)
        {
            this._data = Data;
        }
    }

    public class TcpErrorEventArgs : EventArgs
    {
        private Exception _error;

        #region Properties
        public Exception Error
        {
            get { return this._error; }
        }
        #endregion

        public TcpErrorEventArgs(Exception Error)
        {
            this._error = Error;
        }
    }
}
